#include "pipeline.h"
#include <GL/gl.h>
#include <list>
#include <set>
#include "edge.h"
#include <algorithm>

#include <iostream>

Pipeline::Pipeline()
	:	fillColor(1.0,1.0,1.0),
		lineColor(1.0,1.0,1.0){
	this->fill=true;
}

Pipeline::~Pipeline(){
}

void Pipeline::setFill(bool fill){
	this->fill=fill;
}

bool Pipeline::getFill(){
	return this->fill;
}

void Pipeline::escale (float ex,float ey){
	Matrix3x3 escale(ex,ey,false);
	this->mt*=escale;
}

void Pipeline::translate(float tx, float ty){
	Matrix3x3 translate(tx,ty);
	this->mt*=translate;
}

void Pipeline::rotate(float theta){
	Matrix3x3 rotation(theta);
	this->mt*=rotation;
}

void Pipeline::loadIdentity(){
	this->mt.loadIdentity();
}

void Pipeline::pushMatrix(){
	this->stack.push(this->mt);
}

void Pipeline::popMatrix(){
	this->mt=this->stack.top();
	this->stack.pop();
}

void Pipeline::worldWindow( float top, float left,float right,float  bottom){
	this->window.set(top,left,right,bottom);

	float ax = (this->view.topRight.x-this->view.bottomLeft.x)/(right-left);
	float ay = (this->view.topRight.y-this->view.bottomLeft.y)/(top-bottom);

	float bx = -ax*(left) + this->view.bottomLeft.x;
	float by = -ay*(bottom) + this->view.bottomLeft.y;

	Matrix3x3 windowViewport(ax,ay,bx,by);
	this->mv = windowViewport;
}

void Pipeline::viewport( int top,int left,int right,int bottom ){
	this->view.set(top,left,right,bottom);

	float ax = (this->view.topRight.x-this->view.bottomLeft.x)/(this->window.topRight.x-this->window.bottomLeft.x);
	float ay = (this->view.topRight.y-this->view.bottomLeft.y)/(this->window.topRight.y-this->window.bottomLeft.y);

	float bx = -ax*(this->window.bottomLeft.x) + this->view.bottomLeft.x;
	float by = -ay*(this->window.bottomLeft.y) + this->view.bottomLeft.y;

	Matrix3x3 windowViewport(ax,ay,bx,by);
	this->mv = windowViewport;
}

void Pipeline::setFillColor(Colorf color){
	this->fillColor = color;
}

void Pipeline::setLineColor(Colorf color){
	this->lineColor = color;
}

void Pipeline::fillScanline(Polygon& polygon){
	std::set<Edge,EdgeYCompare> edges;
	// Creo la lista de aristas
	for(unsigned int c=0;c<polygon.size;c++){
		// Salteo las horizontales y uno el ultimo vertice con el primero
		if(c!=(polygon.size-1)){
			Vertexi v1(polygon.list[c]);
			Vertexi v2(polygon.list[c+1]);
			if(v1.y!=v2.y)
				edges.insert(Edge(v1.x,v1.y,v2.x,v2.y,c));
		}else{
			Vertexi v1(polygon.list[c]);
			Vertexi v2(polygon.list[0]);
			if(v1.y!=v2.y)
				edges.insert(Edge(v1.x,v1.y,v2.x,v2.y,c));
		}
	}

	// Nada que pintar.
	if(edges.empty())
		return;


	// Recorro las scanlines que intersecan al poligono nada mas
	int global_maxY = edges.rbegin()->ymax;
	int global_minY = edges.begin()->ymin;

	std::list<Edge> activeEdges;
	std::set<Edge,EdgeYCompare>::iterator currentEdge = edges.begin();

	for(int y=global_minY;y<global_maxY;y++){

		// Eliminar las aristas que fueron recorridas totalmente
		// de la lista de aristas activas(AEL) y  actualizar las otras que esten
		// activas pero no recorridas en su totalidad.
		for(std::list<Edge>::iterator it = activeEdges.begin();it!=activeEdges.end();){
			if(it->ymax<=y){
				std::list<Edge>::iterator eit = it;
				++it;
				activeEdges.erase(eit);
			}else{
				it->stepX();
				++it;
			}
		}

		// Insertar en la AEL las aristas intersecadas por la scanline actual
		while( currentEdge!=edges.end() && currentEdge->ymin==y ){
			if(currentEdge->ymin==y){
				activeEdges.push_back(*currentEdge);
			}
			++currentEdge;
		}
		// Ordeno las aristas segun x creciente( por si se cruzan en algun momento )
		activeEdges.sort();


		// Dibujo los segmentos de a pares segun las aristas activas( Las que estan en la AEL ).
		for(std::list<Edge>::iterator it = activeEdges.begin();it!=activeEdges.end();){
			int x1 = it->getX();
			++it;
			int x2 = it->getX();
			++it;
			horizontalLine(x1,x2,y);
		}

	}
}

void Pipeline::horizontalLine(int x1,int x2,int y){
	for(int x=x1;x<=x2;x++){
		glVertex2i(x,y);
	}
}

void Pipeline::circleBresenham(Vertexf p1,Polygon& polygon){
	int step = 20/*70*/;
	unsigned int nVertices=0;
	std::vector<Vertexf> octVertices[8];
	float e=0;
	float radio = sqrt(p1.x*p1.x + p1.y*p1.y);
	Vertexf vertex(radio,0);

	while(vertex.y<vertex.x){
		if(nVertices%step==0){
			octVertices[0].push_back(Vertexf(vertex.x,vertex.y));
			octVertices[1].push_back(Vertexf(vertex.x,-vertex.y));
			octVertices[2].push_back(Vertexf(vertex.y,-vertex.x));
			octVertices[3].push_back(Vertexf(-vertex.y,-vertex.x));
			octVertices[4].push_back(Vertexf(-vertex.x,-vertex.y));
			octVertices[5].push_back(Vertexf(-vertex.x,vertex.y));
			octVertices[6].push_back(Vertexf(-vertex.y,vertex.x));
			octVertices[7].push_back(Vertexf(vertex.y,vertex.x));
		}

		if ( (2*(e+2*vertex.y+1)) >( (2*vertex.x)-1)){
			vertex.x--;
		}
		vertex.y++;

		e = vertex.x*vertex.x + vertex.y*vertex.y - radio*radio;
		nVertices++;
	}

	polygon.list = new Vertexf[octVertices[0].size()*8];
	polygon.size = octVertices[0].size()*8;
	unsigned int w=0;
	for(unsigned int c=0;c<8;c++){
		if(!(c%2))
			std::reverse(octVertices[c].begin(),octVertices[c].end());
		for(std::vector<Vertexf>::iterator it=octVertices[c].begin();it!=octVertices[c].end();++it,w++){
			polygon.list[w] = (*it);
		}
	}
}

void Pipeline::lineBresenham(Vertexi p1,Vertexi p2){
	int dx 		= p2.x - p1.x;
	int dy 		= p2.y - p1.y;
	int adx		= abs(dx);
	int ady		= abs(dy);
	int e0		= (2*ady) - adx;
	int xk 		= p1.x;
	int yk 		= p1.y;
	int limite 	= p2.x;
	int inc		= 1;
	bool swap = false;

	if( ady<=adx && dx>=0){
		if(dy<0)
			inc = -1;
	}else if( ady>adx && dy>0){
		yk = p1.x;
		xk = p1.y;
		swap = true;
		limite = p2.y;
		std::swap(adx,ady);
		e0	= (2*ady) - adx;
		if(dx<0)
			inc = -1;
	}else if( ady<=adx && dx <0 ){
		xk = p2.x;
		yk = p2.y;
		limite = p1.x;
		// En este caso el limite es al reves porque la recta se construye desde el punto 2
		if(dy>0)
			inc = -1;
	}else if( ady>adx && dy<0){
		yk = p2.x;
		xk = p2.y;
		swap = true;
		limite = p1.y;
		std::swap(adx,ady);
		e0	= (2*ady) - adx;
		// En este caso el limite es al reves porque la recta se construye desde el punto 2
		if(dx>0)
			inc=-1;
	}

	int e = e0;
	while(xk <= limite){
		if(!swap)
			glVertex2i(xk,yk);
		else
			glVertex2i(yk,xk);

		xk++;
		if(e<0){
			e += 2*ady;
		}else{
			yk+=inc;
			e += 2*(ady-adx);
		}
	}
}

bool Pipeline::insideClipRegion(const Vertexf& p,const Viewport& viewport,Side side){
	switch(side){
		case SIDE_TOP:
			return (p.y<=viewport.topRight.y);
		case SIDE_LEFT:
			return (p.x>=viewport.bottomLeft.x);
		case SIDE_RIGHT:
			return (p.x<=viewport.topRight.x);
		case SIDE_BOTTOM:
			return (p.y>=viewport.bottomLeft.y);
		default:
			return true;
	}
}

//                   |    --*
//  (outside)    --- | --/      (inside)
//  (first)   *-/    |          (second)
Vertexf Pipeline::getIntersectPoint(const Vertexf& first,const Vertexf& second,const Viewport& viewport,Side side){
	// Para todo lado existe el caso particular de una linea ortogonal al lado en cuestion
    // lo que provoca que el divisor sea 0 por lo tanto se trata de manera especial.
	switch(side){
		case SIDE_TOP:
			if(first.y-second.y)
				return Vertexf(((first.x-second.x)/(first.y-second.y))*(viewport.topRight.y-second.y) + second.x,viewport.topRight.y);
			else
				return Vertexf(first.x,viewport.topRight.y);
		case SIDE_LEFT:
			if(second.x-first.x)
				return Vertexf(viewport.bottomLeft.x,((second.y-first.y)/(second.x-first.x))*(viewport.bottomLeft.x-first.x) + first.y);
			else
				return Vertexf(viewport.bottomLeft.x,first.y);
		case SIDE_RIGHT:
			if(first.x-second.x)
				return Vertexf(viewport.topRight.x,((first.y-second.y)/(first.x-second.x))*(viewport.topRight.x-second.x) + second.y);
			else
				return Vertexf(viewport.topRight.x,first.y);
		case SIDE_BOTTOM:
			if(second.y-first.y)
				return Vertexf(((second.x-first.x)/(second.y-first.y))*(viewport.bottomLeft.y-first.y) + first.x,viewport.bottomLeft.y);
			else
				return Vertexf(first.x,viewport.bottomLeft.y);
		default:
			return Vertexf();
	}
}

void Pipeline::clipSide(std::vector<Vertexf>& vertices,const Viewport& viewport,Side side){
	Vertexf first;
	Vertexf second;
	Vertexf intersect;
	unsigned int nVertices = vertices.size();
	std::vector<Vertexf> vertices2;

	for(unsigned int c=0;c<nVertices;c++){
		if(c!=(nVertices-1)){
			first= vertices[c];
			second= vertices[c+1];
		}else{
			first= vertices[c];
			second= vertices[0];
		}

		if(insideClipRegion(first,viewport,side)	&&	insideClipRegion(second,viewport,side)){
			vertices2.push_back(second);
		}
		else if(!insideClipRegion(first,viewport,side)	&&	insideClipRegion(second,viewport,side)){
			intersect = getIntersectPoint(first,second,viewport,side);
			vertices2.push_back(intersect);
			vertices2.push_back(second);
		}
		else if(insideClipRegion(first,viewport,side)	&&	!insideClipRegion(second,viewport,side)){
			intersect = getIntersectPoint(second,first,viewport,side);
			vertices2.push_back(intersect);
		}
	}

	vertices = vertices2;
}

void Pipeline::shClip(Polygon& polygon,const Viewport& viewport){
	std::vector<Vertexf> vertices;
	vertices.reserve(polygon.size);
	for(unsigned int c=0;c<polygon.size;++c){
		vertices.push_back(polygon.list[c]);
	}
	clipSide(vertices,viewport,SIDE_TOP);
	clipSide(vertices,viewport,SIDE_LEFT);
	clipSide(vertices,viewport,SIDE_RIGHT);
	clipSide(vertices,viewport,SIDE_BOTTOM);

	delete[](polygon.list);
	polygon.list = NULL;
	polygon.size = vertices.size();
	if(polygon.size==0)
		return;

	polygon.list= new Vertexf[vertices.size()];
	for(unsigned int c=0;c<vertices.size();++c)
		polygon.list[c] = vertices[c];
}


bool Pipeline::lbClip(Vertexf* line,const Viewport& viewport){
	float dx = line[1].x-line[0].x;
	float dy = line[1].y-line[0].y;
	float p[4] = { -dx,dx,-dy,dy };
	float q[4] = {  line[0].x-(float)viewport.bottomLeft.x,(float)viewport.topRight.x-line[0].x,line[0].y-(float)viewport.bottomLeft.y,(float)viewport.topRight.y-line[0].y};
	float r[4];
	float t0 = 0;
	float t1 = 1;

	for(unsigned k=0;k<4;k++){
		if(!(p[k])){
			if( (q[k]<0))
				return false;
		}
		r[k] = q[k]/p[k];

		if(p[k]<0){
			if( (r[k]>t1) )
				return false;
			else if(r[k]>t0){
				t0 = r[k];
			}
		}else if(p[k]>0){
			if( (r[k]<t0) )
				return false;
			else if(r[k]<t1){
				t1 = r[k];
			}
		}
	}

	if(t1 < 1){
  		line[1].x = line[0].x + t1*dx;
  		line[1].y = line[0].y + t1*dy;
	}

	if(t0 > 0){
  		line[0].x = line[0].x + t0*dx;
  		line[0].y = line[0].y + t0*dy;
	}

	return true;
}

void Pipeline::draw(Type type, Vertexf* list,unsigned int size){
	switch(type){
		case POINTS:
			drawPoints(list,size);
			return;
		case LINES:
			drawLines(list,size);
			return;
		case CIRCLES:
			drawCircles(list,size);
			return;
		case POLYGON:
			Polygon polygon(list,size);
			drawPolygon(polygon);
			return;
	}
}

void Pipeline::drawPoints(Vertexf* list,unsigned int size){
	std::vector<Vertexf> vertices(size);
	for (unsigned int i=0; i<size;i++){
		Vector vector=this->mv*(this->mt*(Vector)list[i]);
		Vertexf vertex(vector);
		if(	vertex.x >= this->window.bottomLeft.x	&&
			vertex.x <= this->window.topRight.x	&&
			vertex.y >= this->window.bottomLeft.y	&&
			vertex.y <= this->window.topRight.y	){
				vertices.push_back(vertex);
		}
	}

	glColor3f(lineColor.r,lineColor.g,lineColor.b);
	glBegin(GL_POINTS);
		for(unsigned int c=0;c<vertices.size();c++){
			Vertexi vertexi(vertices[c]);
			glVertex2i(vertexi.x,vertexi.y);
		}
	glEnd();
}

void Pipeline::drawLines(Vertexf* list,unsigned int size){
	std::vector<Vertexf> vertices(size);
	for (unsigned int i=0; i<size;i+=2){
		Vector vector1=this->mv*(this->mt*(Vector)list[i]);
		Vector vector2=this->mv*(this->mt*(Vector)list[i+1]);

		Vertexf lineVertices[2] = { Vertexf(vector1),Vertexf(vector2) };

		if(	lbClip(lineVertices,this->view)	){
				vertices.push_back(lineVertices[0]);
				vertices.push_back(lineVertices[1]);
		}
	}

	glColor3f(lineColor.r,lineColor.g,lineColor.b);
	glBegin(GL_POINTS);
		for(unsigned int c=0;c<vertices.size();c+=2){
			lineBresenham(vertices[c],vertices[c+1]);
		}
	glEnd();
}

void Pipeline::drawCircles(Vertexf* list,unsigned int size){
	Polygon poly;
	for(unsigned int c=0;c<size;c++){
		circleBresenham(list[c],poly);
		drawPolygon(poly);
	}
}

void Pipeline::drawPolygon( Polygon& polygon ){
	for (unsigned int i=0; i<polygon.size;i++){
		Vector vector=this->mv*(this->mt*(Vector)polygon.list[i]);
		polygon.list[i] = Vertexf(vector);
	}
	shClip(polygon,this->view);
	if(polygon.size==0)
		return;

	glBegin(GL_POINTS);
		if(!fill){
			glColor3f(lineColor.r,lineColor.g,lineColor.b);
			unsigned int c=0;
			for(;c<polygon.size-1;c++){
				lineBresenham(polygon.list[c],polygon.list[c+1]);
			}
			lineBresenham(polygon.list[c],polygon.list[0]);
		}else{
			glColor3f(fillColor.r,fillColor.g,fillColor.b);
			fillScanline(polygon);
		}
	glEnd();
}

